package chapter21.section8;

import chapter19.Course;
import chapter19.Food;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

/**
 * Created with IntelliJ IDEA.
 * Description:
 *
 * @author liuziyang
 * @create 2019-02-06 11:13
 */
class Order {
    private static int counter = 0;
    private final int id = counter++;
    private final Customer2 customer;
    private final WaitPerson2 waitPerson;
    private final Food food;

    public Order(Customer2 customer, WaitPerson2 waitPerson, Food food) {
        this.customer = customer;
        this.waitPerson = waitPerson;
        this.food = food;
    }

    public Food item() {
        return food;
    }

    public Customer2 getCustomer() {
        return customer;
    }

    public WaitPerson2 getWaitPerson() {
        return waitPerson;
    }

    @Override
    public String toString() {
        return "Order: " + id + " item: " + food + " for: " + customer + " served by: " + waitPerson;
    }
}

class Plate {
    private final Order order;
    private final Food food;

    public Plate(Order order, Food food) {
        this.order = order;
        this.food = food;
    }

    public Order getOrder() {
        return order;
    }

    public Food getFood() {
        return food;
    }

    @Override
    public String toString() {
        return food.toString();
    }
}

class Customer2 implements Runnable {
    private static int count = 0;
    private final int id = count++;
    private final WaitPerson2 waitPerson;
    private final SynchronousQueue<Plate> placeSetting = new SynchronousQueue<>();

    public Customer2(WaitPerson2 waitPerson) {
        this.waitPerson = waitPerson;
    }

    public void deliver(Plate plate) throws InterruptedException {
        placeSetting.put(plate);
    }

    @Override
    public void run() {
        for (Course course : Course.values()) {
            Food food = course.randomSelection();
            try {
                waitPerson.placeOrder(this, food);
                System.out.println(this + " eating " + placeSetting.take());
            } catch (InterruptedException e) {
                System.out.println(this + " waiting for " + course + " interrupted");
            }
        }
        System.out.println(this + " finishing meal, leaving");
    }

    @Override
    public String toString() {
        return "Customer " + id + " ";
    }
}

class WaitPerson2 implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final Restaurant2 restaurant;
    BlockingQueue<Plate> filledOrders = new LinkedBlockingDeque<>();

    public WaitPerson2(Restaurant2 restaurant) {
        this.restaurant = restaurant;
    }

    public void placeOrder(Customer2 customer, Food food) {
        try {
            restaurant.orders.put(new Order(customer, this, food));
        } catch (InterruptedException e) {
            System.out.println(this + " placeOrder interrupted");
        }
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Plate plate = filledOrders.take();
                System.out.println(this + " received " + plate + " delivering to " + plate.getOrder().getCustomer());
                plate.getOrder().getCustomer().deliver(plate);
            }
        } catch (InterruptedException e) {
            System.out.println(this + " interrupted");
        }
        System.out.println(this + " off duty");
    }

    @Override
    public String toString() {
        return "WaitPerson " + id + " ";
    }
}

class Chef2 implements Runnable {
    private static int counter = 0;
    private final int id = counter++;
    private final Restaurant2 restaurant;
    private static final Random random = new Random(47);

    public Chef2(Restaurant2 restaurant) {
        this.restaurant = restaurant;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                Order order = restaurant.orders.take();
                Food requestedItem = order.item();
                TimeUnit.MILLISECONDS.sleep(random.nextInt(500));
                Plate plate = new Plate(order, requestedItem);
                order.getWaitPerson().filledOrders.put(plate);
            }
        } catch (InterruptedException e) {
            System.out.println(this + " interrupted");
        }
        System.out.println(this + " off duty");
    }

    @Override
    public String toString() {
        return "Chef " + id + " ";
    }
}

class Restaurant2 implements Runnable {
    private static final Random random = new Random(47);
    private final List<WaitPerson2> waitPersons = new ArrayList<>();
    private final List<Chef2> chefs = new ArrayList<>();
    private final ExecutorService exec;
    BlockingQueue<Order> orders = new LinkedBlockingDeque<>();

    public Restaurant2(ExecutorService exec, int nWaitPersons, int nChefs) {
        this.exec = exec;
        for (int i = 0; i < nWaitPersons; i++) {
            WaitPerson2 person = new WaitPerson2(this);
            waitPersons.add(person);
            exec.execute(person);
        }

        for (int i = 0; i < nChefs; i++) {
            Chef2 chef2 = new Chef2(this);
            chefs.add(chef2);
            exec.execute(chef2);
        }
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                WaitPerson2 wp = waitPersons.get(random.nextInt(waitPersons.size()));
                Customer2 customer2 = new Customer2(wp);
                exec.execute(customer2);
                TimeUnit.MILLISECONDS.sleep(100);
            }
        } catch (InterruptedException e) {
            System.out.println("Restaurant interrupted");
        }

        System.out.println("Restaurant closing");
    }
}


public class RestaurantWithQueues {
    public static void main(String[] args) throws IOException {
        ExecutorService exec = Executors.newCachedThreadPool();
        Restaurant2 restaurant = new Restaurant2(exec, 5, 2);
        exec.execute(restaurant);
        System.out.println("press enter to quit");
        System.in.read();
        exec.shutdownNow();
    }
}
